package com.cskaoyan.mall.payment.controller;

import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import com.cskaoyan.mall.common.constant.RedisConst;
import com.cskaoyan.mall.common.result.Result;
import com.cskaoyan.mall.order.dto.OrderInfoDTO;
import com.cskaoyan.mall.pay.api.dto.PaymentInfoDTO;
import com.cskaoyan.mall.payment.client.OrderApiClient;
import com.cskaoyan.mall.payment.constant.PaymentType;
import com.cskaoyan.mall.payment.pay.alipay.CsmallAlipayConfig;
import com.cskaoyan.mall.payment.service.PayService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

/**
 * @Author Pudding
 * @Date 2024/7/17 15:05
 */
@Slf4j
@Controller
public class PayController {

    @Autowired
    OrderApiClient orderApiClient;
    @Autowired
    PayService payService;
    @Autowired
    CsmallAlipayConfig csmallAlipayConfig;
    @Autowired
    RedissonClient redissonClient;

    /**
     * 1. 通过订单id获取订单信息
     *
     * @param orderId
     * @return
     */
    @GetMapping("/pay/auth")
    @ResponseBody
    public Result getOrderInfo(@RequestParam("orderId") Long orderId) {

        //远程调用订单服务接口，获取订单消息
        OrderInfoDTO orderInfoDTO = orderApiClient.getOrderInfoDTO(orderId);

        return Result.ok(orderInfoDTO);
    }


    /**
     * 2. 去支付，获取支付页面
     */
    @RequestMapping("/pay/alipay/submit/{orderId}")
    @ResponseBody
    public String submitOrder(@PathVariable Long orderId) {

        // 该字段应该是前端传入
        String payTypeName = "Alipay";

        return payService.createPay(orderId, payTypeName);
    }

    /**
     * 3. 支付成功：同步回调请求
     */
    @RequestMapping("/pay/alipay/alipay/callback/return")
    public String callBack() {

        log.info("支付成功，同步回调！");

        // 同步回调给用户展示信息
        return "redirect:" + "http://localhost:8000/#/pay/success";
    }


    /**
     * 4. 支付成功：异步回调请求
     * a) 校验异步通知合法性
     *      1. 验证签名
     *      2. 验证业务参数
     *      3. 幂等性校验（校验该笔支付，系统是不是已经处理过了，处理过了就不再重复处理了）
     * b) 要完成响应的业务
     *      1. 修改支付记录
     *      2. 修改订单状态
     *      3. 扣减库存
     *      4. 拆单
     */
    @PostMapping("/pay/alipay/callback/notify")
    @ResponseBody
    public String callbackNotify(@RequestParam Map<String, String> paramsMap) throws AlipayApiException {

        log.info("支付成功，异步回调！");

        //a) 校验异步通知合法性
        //1. 验证签名
        boolean signVerified = AlipaySignature.rsaCheckV1(paramsMap, csmallAlipayConfig.getAlipayPublicKey(), CsmallAlipayConfig.charset, CsmallAlipayConfig.sign_type); //调用SDK验证签名
        if (!signVerified) {
            return "验证签名：failure";
        }
        //2. 验证业务参数
        //2.1 商家需要验证该通知数据中的 out_trade_no 是否为商家系统中创建的订单号
        String outTradeNo = paramsMap.get("out_trade_no");
        PaymentInfoDTO paymentInfoDTO = payService.queryPaymentInfoByOutTradeNoAndPaymentType(outTradeNo, PaymentType.ALIPAY.name());
        if (paymentInfoDTO == null) {
            return "订单号校验：failure";
        }
        //2.2 判断 total_amount 是否确实为该订单的实际金额（即商家订单创建时的金额）
        String totalAmount = paramsMap.get("total_amount");
        if (paymentInfoDTO.getTotalAmount().doubleValue() != Double.valueOf(totalAmount)) {
            return "订单金额校验：failure";
        }
        //2.3 验证 app_id 是否为该商家本身
        String appId = paramsMap.get("app_id");
        if (!appId.equals(csmallAlipayConfig.getAppId())) {
            return "appId校验：failure";
        }
        //2.4 验证 trade_status为TRADE_SUCCESS，并判断该笔交易是否在商户网站中已经做过处理（即商户订单号是否已经存在）
        String tradeStatus = paramsMap.get("trade_status");
        if (!tradeStatus.equals("TRADE_SUCCESS")) {
            return "交易状态：failure";
        }
        //3. 幂等性校验
        //幂等：1^0 = 1^1 = 1^10 = 1^n = 1
        //幂等接口：接口使用同样的参数被调用一次和若干次，每一次得到的返回值是一样的，对系统的影响也要一致
        //对于查询的接口而言，天生就是幂等接口
        //对于增删改的接口而言，天生不是幂等接口，如果我们希望对应的接口成为幂等接口需要我们做额外的处理
        String notifyId = paramsMap.get("notify_id");  //支付宝异步通知参数中的 notify_id 始终是不变的，所以我们用在幂等性校验
        //将notify_id 作为幂等性校验的参数放入redis中
        String key = RedisConst.PAY_CALL_BACK_VERFY_PREFIX + notifyId;  //"pay:callback:notifyid:"
        RBucket<Object> bucket = redissonClient.getBucket(key);
        boolean ret = bucket.trySet(outTradeNo);
        // 如果redis中这个标记存在，说明之前已经回调过了，那么ret结果为false
        if (!ret) {
            return "success";
        }
        // 如果后续的业务逻辑在处理业务的时候，发生异常，需要删除幂等标记


        //b) 要完成响应的业务
        //1. 修改支付记录表 ——> 其中要处理：修改订单状态、扣减库存、拆单
        Boolean flag = payService.successPay(outTradeNo, PaymentType.ALIPAY.name(), paramsMap);
        if (!flag){
            // 业务没有处理成功，删除幂等标记
            bucket.delete();
            return "业务处理状况：failure";
        }

        return "success";
    }


}
